﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;

namespace AttributesFramework.Util
{
    internal class CustomProxy : RealProxy, IDisposable
    {
        public CustomProxy(object p_Subject)
            : base(p_Subject.GetType())
        {
            AttachServer((MarshalByRefObject)p_Subject);
        }

        public static object Instance(Object p_Object)
        {
            return new CustomProxy(p_Object).GetTransparentProxy();
        }

        public void Dispose()
        {
            DetachServer();
        }

        public override IMessage Invoke(IMessage p_Message)
        {
            MethodCallMessageWrapper _methodCall = new MethodCallMessageWrapper((IMethodCallMessage)p_Message);
            MethodInfo _methodInfo = (MethodInfo)_methodCall.MethodBase;
            MarshalByRefObject _parentObject = GetUnwrappedServer();

            IMessage _returnValue = null;
            bool _isGet = false;
            object _outValue = null;

            if (_parentObject != null)
            {
                PropertyInfo _propertyInfo = GetMethodProperty(_methodInfo, _parentObject, out _isGet);

                if (_propertyInfo != null)
                {
                    if (_isGet)
                    {
                        _outValue = _methodInfo.Invoke(_parentObject, _methodCall.Args);
                        
                        // Getter
                        if (_propertyInfo.IsDefined(typeof(Attributes.DBRelationAttribute), false))
                        {
                            Watcher.setPropertyAccessed(_parentObject.GetHashCode(), _propertyInfo.Name);

                            Attributes.DBTableAttribute _classTable = Mapper.Attributes.GetDBTableAttribute(_parentObject.GetType());
                            Attributes.DBColumnAttribute _pkAttribute = Mapper.Attributes.GetDBColumnsPrimaryKeys(_parentObject.GetType())[0];
                            Attributes.DBRelationAttribute _dbColumn = (Attributes.DBRelationAttribute)_propertyInfo.GetCustomAttributes(typeof(Attributes.DBRelationAttribute), false)[0];

                            if (_dbColumn.RelationType == AttributesFramework.Attributes.DBRelationType.ONE_ONE)
                            {
                                Attributes.DBColumnAttribute _childPkAttribute = Mapper.Attributes.GetDBColumnsPrimaryKeys(_dbColumn.ClassType)[0];

                                if (Mapper.Attributes.GetDBColumnAttributes(_dbColumn.ClassType).Count > 1)
                                {
                                    Attributes.DBColumnAttribute _firstAttribute = Mapper.Attributes.GetDBColumnAttributes(_dbColumn.ClassType)[1];

                                    object _dirtyChild = _parentObject.GetType().GetProperty(_propertyInfo.Name).GetValue(_parentObject, null);

                                    if (_dirtyChild != null)
                                    {
                                        PropertyInfo[] _properties = _dirtyChild.GetType().GetProperties();
                                        if (_properties.Length > 1)
                                        {
                                            if ((_properties[1].GetValue(_dirtyChild, null) == null) || (_properties[1].GetValue(_dirtyChild, null).ToString() == Mapper.Util.GetEmptyValue(_firstAttribute.DataType).Replace("'", "")))
                                            {
                                                ArrayList _searchResult = Mapper.Data.RetrieveByQuerySingleThread(Mapper.Data.getCurrentTransaction(), "SELECT * FROM " + Mapper.Attributes.GetDBTableAttribute(_dbColumn.ClassType).Name + " WHERE " + _childPkAttribute.Name + " = " + Mapper.Util.GetSQLValue(_dirtyChild, _childPkAttribute), _dbColumn.ClassType);
                                                if (_searchResult.Count > 0)
                                                {
                                                    _outValue = _searchResult[0];
                                                    _parentObject.GetType().GetProperty(_propertyInfo.Name).SetValue(_parentObject, _outValue, null);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            else if (_dbColumn.RelationType == AttributesFramework.Attributes.DBRelationType.ONE_N)
                            {
                                if (Convert.ToInt32(_outValue.GetType().GetMethod("get_Count").Invoke(_outValue, null)) == 0)
                                {
                                    foreach (object _child in Mapper.Data.RetrieveByQuerySingleThread(Mapper.Data.getCurrentTransaction(), "SELECT * FROM " + Mapper.Attributes.GetDBTableAttribute(_dbColumn.ClassType).Name + " WHERE " + _pkAttribute.Name + "_" + _classTable.Name + " = " + Mapper.Util.GetSQLValue(_parentObject, _pkAttribute), _dbColumn.ClassType))
                                    {
                                        _outValue.GetType().GetMethod("Add").Invoke(_outValue, new object[] { _child });
                                    }

                                    _parentObject.GetType().GetProperty(_propertyInfo.Name).SetValue(_parentObject, _outValue, null);
                                }
                            }
                            else if (_dbColumn.RelationType == AttributesFramework.Attributes.DBRelationType.N_N)
                            {
                                if (Convert.ToInt32(_outValue.GetType().GetMethod("get_Count").Invoke(_outValue, null)) == 0)
                                {
                                    Attributes.DBTableAttribute _childTable = Mapper.Attributes.GetDBTableAttribute(_dbColumn.ClassType);
                                    Attributes.DBColumnAttribute _childPkAttribute = Mapper.Attributes.GetDBColumnsPrimaryKeys(_dbColumn.ClassType)[0];

                                    string _relationTableName = _classTable.Name + "_" + _childTable.Name + "_" + _dbColumn.Name;

                                    if (_childTable.Name == _classTable.Name)
                                    {
                                        foreach (object _child in Mapper.Data.RetrieveByQuerySingleThread(Mapper.Data.getCurrentTransaction(), "SELECT * FROM " + Mapper.Attributes.GetDBTableAttribute(_dbColumn.ClassType).Name + " WHERE " + _childPkAttribute.Name + " IN (SELECT " + _childPkAttribute.Name + "_" + _childTable.Name + "_2 FROM " + _relationTableName + " WHERE " + _pkAttribute.Name + "_" + _classTable.Name + "_1 = " + Mapper.Util.GetSQLValue(_outValue, _pkAttribute) + ")", _dbColumn.ClassType))
                                        {
                                            _outValue.GetType().GetMethod("Add").Invoke(_outValue, new object[] { _child });
                                        }
                                    }
                                    else
                                    {
                                        foreach (object _child in Mapper.Data.RetrieveByQuerySingleThread(Mapper.Data.getCurrentTransaction(), "SELECT * FROM " + Mapper.Attributes.GetDBTableAttribute(_dbColumn.ClassType).Name + " WHERE " + _childPkAttribute.Name + " IN (SELECT " + _childPkAttribute.Name + "_" + _childTable.Name + " FROM " + _relationTableName + " WHERE " + _pkAttribute.Name + "_" + _classTable.Name + " = " + Mapper.Util.GetSQLValue(_outValue, _pkAttribute) + ")", _dbColumn.ClassType))
                                        {
                                            _outValue.GetType().GetMethod("Add").Invoke(_outValue, new object[] { _child });
                                        }
                                    }

                                    _parentObject.GetType().GetProperty(_propertyInfo.Name).SetValue(_parentObject, _outValue, null);
                                }
                            }
                        }
                    }
                    else
                    {
                        // Setter
                        object _inputParmameter = _methodCall.InArgs[0];

                        _methodCall.Args[0] = _inputParmameter;
                        _outValue = _methodInfo.Invoke(_parentObject, _methodCall.Args);

                        Watcher.setPropertyDirty(_parentObject, _propertyInfo.Name);
                    }
                }
                else
                {
                    _outValue = _methodInfo.Invoke(_parentObject, _methodCall.Args);
                }
            }

            _returnValue = new ReturnMessage(_outValue, _methodCall.Args, _methodCall.Args.Length, _methodCall.LogicalCallContext, _methodCall);

            return _returnValue;
        }

        protected static PropertyInfo GetMethodProperty(MethodInfo p_MethodInfo, object p_ParentObject, out bool p_isGet)
        {
            foreach (PropertyInfo aProp in p_ParentObject.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                MethodInfo _methodInfo = null;

                _methodInfo = aProp.GetGetMethod(true);
                if (_methodInfo != null && _methodInfo.Equals(p_MethodInfo))
                {
                    p_isGet = true;
                    return aProp;
                }

                _methodInfo = aProp.GetSetMethod(true);

                if (_methodInfo != null && _methodInfo.Equals(p_MethodInfo))
                {
                    p_isGet = false;
                    return aProp;
                }
            }

            p_isGet = false;
            return null;
        }
    }
}
